#pragma once
#include "dispatcher.h"
#include "TubeTruckTypes.h"
#include "Group.h"
#include "State.h"
#include "TTSlave.h"
#include "Socket.h"
#include <pthread.h>

#include "Identity.h"

namespace TTP {




   // Totally Terrible Infection
   class TTI {
   private:
      template<class A> friend void processPacket(A *);
      friend void* worker(void*);

      TTI(const TTI&) {}

      TTI() : sock(Socket::get()), mGroupCloner(Group::groupCloner),
       mGroupFactory(Group::groupFactory), mStateCloner(State::stateCloner),
       mStateFactory(State::stateFactory) {
      }

      // thread stuff
      static TTI* tti_inst;
      static pthread_mutex_t lock;
      static bool initialized;
      static pthread_t pid;
      static int pipe[2];

      // state
      Socket * sock;
      id_hash mCurGroup;
      std::map<Address, std::map<id_hash, Group*> > mGroups;
      std::map<id_hash, State*> mStates;

      // mVoteMap[A][B] --> A votes for B
      std::map<id_hash, std::map<id_hash, int>> mVoteMap;
      std::map<id_hash, std::map<id_hash, ack_num>> mAckMap;
      std::map<id_hash, Crypt::Identity*> mApplicants;

      std::map<ack_num, Packet*> mHistory;
      std::vector<Packet*> outbox;
      enum tti_state {OUTCAST, LOBBY, JOINED};
      tti_state mTTI_State;

      // callbacks
      void (*mIISSend)(std::vector<byte>&);
      bool (*mHASFLAVRSend)(const Crypt::Identity *);
      void (*mCHZRecv)(const State*);
      void (*mIISRecv)(const Group*, Crypt::Identity& , const std::vector<byte>&);
      void (*mDOWRecv)(const Group*);
      void (*mLWLRecv)(const Group*);
      Group * (*mGroupFactory)();
      Group * (*mGroupCloner)(const Group &);
      State * (*mStateFactory)();
      State * (*mStateCloner)(const State &);
      Crypt::Identity * (mIdentFactory)();
      Crypt::Identity * (mIdentCloner)(const Crypt::Identity &);

      // crypto
      Crypt::RSA * mKey;
      id_hash mID;

      // methods
      void _changeState(tti_state s) {
         // TODO Add correct packets to outbox

         switch(mTTI_State) {
            case OUTCAST:
               switch(s) {
                  case OUTCAST: break;
                  case LOBBY: break;
                  case JOINED: break;
               }
               break;
            case LOBBY:
               switch(s) {
                  case OUTCAST: break;
                  case LOBBY: break;
                  case JOINED: break;
               }
               break;
            case JOINED:
               switch(s) {
                  case OUTCAST: break;
                  case LOBBY: break;
                  case JOINED: break;
               }
               break;
         }
         mTTI_State = s;
      }

      inline Group* _curGroup() {
         Address a = sock->getDestAddress();
         if(mGroups.find(a) != mGroups.end()) {
            return mGroupCloner(*mGroups[a][mCurGroup]);
         } else {
            return NULL;
         }
      }

      bool _signaled() {
         timeval tmp = {0, 0};
         fd_set fds;
         FD_ZERO(&fds);
         FD_SET(pipe[0], &fds);
         return select(pipe[0]+1, &fds, 0, 0, &tmp); // 1 or 0
      }

      void _clrsignal() {
         static byte tmp[128];

         // TODO
      }

      void _signal() { char nop; write(pipe[1], &nop, 1); }

   public:

      void initialize() {
         lockObj lk(&lock);
         if(initialized) throw msg_exception("Trying to initialize an already-initialized TTI!");
         initialized = true;

         mKey = new Crypt::RSA();

         pthread_create(&pid, 0, worker, 0);
      }

      Group* curGroup() {
         lockObj lk(&lock);
         lockObj lk2(&sock->lock);
         return _curGroup();
      }

      inline static TTI* get() {
         // lockObj lk(&lock): for singleton to work in many places
         // can't lock.... possible race.  HACK
         if(!tti_inst) tti_inst = new TTI();
         return tti_inst;
      }

      void getGroups(std::map<Address, std::map<id_hash, Group*>>& rslt) {
         lockObj lk(&lock);

         FOR_EACH(grp, mGroups) {
            FOR_EACH(g, grp->second) {
               rslt[grp->first][g->first] = mGroupCloner(*g->second);
            }
         }
      }

      void changeGroups(Group* g) {
         lockObj lk(&lock);

         raise(SIGUSR1); 
         // unlock recv loop, which will block on lock in sendOutgoingPackets
         // we'll wait for the thread to unlock socket.

         lockObj lk2(&sock->lock);

         sock->connect(g->getAddress());
         _changeState(OUTCAST);

         // TTI unlocks, packets are in outbox.
      }

      void regGetCheez(void (sc)(const State *)) {
         lockObj lk(&lock);
         mCHZRecv = sc;
      }

      void regIISRecv(void (*sc)(const Group*, Crypt::Identity& , const std::vector<byte>&)) {
         lockObj lk(&lock);
         mIISRecv = sc;
      }

      void regIISSend(void (sc)(std::vector<byte>&)) {
         lockObj lk(&lock);
         mIISSend = sc;
      }

      void regDWGet(void (sc)(const Group *)) {
         lockObj lk(&lock);
         mDOWRecv = sc;
      }

      void regLAWLGet(void (sc)(const Group *)) {
         lockObj lk(&lock);
         mLWLRecv = sc;
      }

      void regState(State *s) {
         lockObj lk(&lock);
         mStates[mID] = s;
      }

      void regGroupFactory(Group * (gf)()) {
         lockObj lk(&lock);
         mGroupFactory = gf;
      }

      void regGroupCloner(Group * (gc)(const Group&)) {
         lockObj lk(&lock);
         mGroupCloner = gc;
      }

      void regStateFactory(State * (sf)()) {
         lockObj lk(&lock);
         mStateFactory = sf;
      }

      void regStateCloner(State * (sc)(const State&)) {
         lockObj lk(&lock);
         mStateCloner = sc;
      }

      inline ~TTI() {
         lockObj lk(&lock);
         assert(pid != NULL && "Trying to delete TTI twice!");
         pthread_join(pid, NULL);
         pid = NULL;

         // TODO clear oHistory
         // TODO clear oStates

         delete mKey;
         delete sock;
         
         tti_inst = NULL;
      }
   };

   TTI* TTI::tti_inst = NULL;
   pthread_t TTI::pid = NULL;
   pthread_mutex_t TTI::lock = PTHREAD_MUTEX_INITIALIZER;
   bool TTI::initialized = false;
}

